// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package work

import (
	"bufio"
	"bytes"
	"fmt"
	"internal/buildcfg"
	"io"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/fsys"
	"cmd/go/internal/load"
	"cmd/go/internal/str"
	"cmd/internal/objabi"
	"cmd/internal/quoted"
	"cmd/internal/sys"
	"crypto/sha1"
)

// 指定-trimpath时用于GOROOT_FINAL的“路径”
const trimPathGoRootFinal = "go"

var runtimePackages = map[string]struct{}{
	"internal/abi":            struct{}{},
	"internal/bytealg":        struct{}{},
	"internal/cpu":            struct{}{},
	"internal/goarch":         struct{}{},
	"internal/goos":           struct{}{},
	"runtime":                 struct{}{},
	"runtime/internal/atomic": struct{}{},
	"runtime/internal/math":   struct{}{},
	"runtime/internal/sys":    struct{}{},
}

// Go工具链。

type gcToolchain struct{}

func (gcToolchain) compiler() string {
	return base.Tool("compile")
}

func (gcToolchain) linker() string {
	return base.Tool("link")
}

func pkgPath(a *Action) string {
	p := a.Package
	ppath := p.ImportPath
	if cfg.BuildBuildmode == "plugin" {
		ppath = pluginPath(a)
	} else if p.Name == "main" && !p.Internal.ForceLibrary {
		ppath = "main"
	}
	return ppath
}

func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, gofiles []string) (ofile string, output []byte, err error) {
	p := a.Package
	objdir := a.Objdir
	if archive != "" {
		ofile = archive
	} else {
		out := "_go_.o"
		ofile = objdir + out
	}

	pkgpath := pkgPath(a)
	defaultGcFlags := []string{"-p", pkgpath}
	if p.Module != nil {
		v := p.Module.GoVersion
		if v == "" {
			// 我们开始在go中添加“go”指令。mod file无条件地
			// 从Go 1.12开始，因此任何仍然缺少此类指令的模块必须
			// 或者在此之前已经编写过，或者手工编辑过Go。mod 
			// 该文件自编辑后未被cmd/go更新。
			// 
			// 不幸的是，至少通过Go 1.16，我们没有向
			// 供应商/模块添加版本。txt。所以这也可能是一个1.16版本的依赖项。
			// 
			// 幸运的是，Go-
			// 1.11和1.16之间的语言没有突破性的变化，所以如果我们假设Go 1.16语义，我们将不会引入任何虚假错误——我们只会掩盖错误，而不是特别是
			// 重要的错误。
			v = "1.16"
		}
		if allowedVersion(v) {
			defaultGcFlags = append(defaultGcFlags, "-lang=go"+v)
		}
	}
	if p.Standard {
		defaultGcFlags = append(defaultGcFlags, "-std")
	}
	_, compilingRuntime := runtimePackages[p.ImportPath]
	compilingRuntime = compilingRuntime && p.Standard
	if compilingRuntime {
		// 运行时使用一个特殊的gc标志进行编译，以检查
		// 运行时包中无效的内存分配，
		// 并实现一些特殊的编译器杂注。
		defaultGcFlags = append(defaultGcFlags, "-+")
	}

	// 如果我们给编译器整个包（没有C等文件），告诉它，
	// 这样它就可以给出关于转发声明的正确错误消息。
	// 例外：一些标准软件包对由软件包运行时在后台提供的
	// 片段进行了正向声明。
	extFiles := len(p.CgoFiles) + len(p.CFiles) + len(p.CXXFiles) + len(p.MFiles) + len(p.FFiles) + len(p.SFiles) + len(p.SysoFiles) + len(p.SwigFiles) + len(p.SwigCXXFiles)
	if p.Standard {
		switch p.ImportPath {
		case "bytes", "internal/poll", "net", "os":
			fallthrough
		case "runtime/metrics", "runtime/pprof", "runtime/trace":
			fallthrough
		case "sync", "syscall", "time":
			extFiles++
		}
	}
	if extFiles == 0 {
		defaultGcFlags = append(defaultGcFlags, "-complete")
	}
	if cfg.BuildContext.InstallSuffix != "" {
		defaultGcFlags = append(defaultGcFlags, "-installsuffix", cfg.BuildContext.InstallSuffix)
	}
	if a.buildID != "" {
		defaultGcFlags = append(defaultGcFlags, "-buildid", a.buildID)
	}
	if p.Internal.OmitDebug || cfg.Goos == "plan9" || cfg.Goarch == "wasm" {
		defaultGcFlags = append(defaultGcFlags, "-dwarf=false")
	}
	if strings.HasPrefix(runtimeVersion, "go1") && !strings.Contains(os.Args[0], "go_bootstrap") {
		defaultGcFlags = append(defaultGcFlags, "-goversion", runtimeVersion)
	}
	if symabis != "" {
		defaultGcFlags = append(defaultGcFlags, "-symabis", symabis)
	}

	gcflags := str.StringList(forcedGcflags, p.Internal.Gcflags)
	if p.Internal.FuzzInstrument {
		gcflags = append(gcflags, fuzzInstrumentFlags()...)
	}
	if compilingRuntime {
		// 删除-N，如果存在。
		// 不进行优化就无法构建运行时，因为编译器无法消除足够的写障碍。
		for i := 0; i < len(gcflags); i++ {
			if gcflags[i] == "-N" {
				copy(gcflags[i:], gcflags[i+1:])
				gcflags = gcflags[:len(gcflags)-1]
				i--
			}
		}
	}
	// 如果可能，添加-c=N以使用并发后端编译。
	if c := gcBackendConcurrency(gcflags); c > 1 {
		gcflags = append(gcflags, fmt.Sprintf("-c=%d", c))
	}

	args := []any{cfg.BuildToolexec, base.Tool("compile"), "-o", ofile, "-trimpath", a.trimpath(), defaultGcFlags, gcflags}
	if p.Internal.LocalPrefix == "" {
		args = append(args, "-nolocalimports")
	} else {
		args = append(args, "-D", p.Internal.LocalPrefix)
	}
	if importcfg != nil {
		if err := b.writeFile(objdir+"importcfg", importcfg); err != nil {
			return "", nil, err
		}
		args = append(args, "-importcfg", objdir+"importcfg")
	}
	if embedcfg != nil {
		if err := b.writeFile(objdir+"embedcfg", embedcfg); err != nil {
			return "", nil, err
		}
		args = append(args, "-embedcfg", objdir+"embedcfg")
	}
	if ofile == archive {
		args = append(args, "-pack")
	}
	if asmhdr {
		args = append(args, "-asmhdr", objdir+"go_asm.h")
	}

	for _, f := range gofiles {
		f := mkAbs(p.Dir, f)

		// 处理覆盖层。使用OverlayPath 
		// /转换路径名，这样就可以将这些路径直接传递给工具。
		// 之前扫描目录时，删除的文件不会显示在中，因此OverlyPath将永远不会在此处返回“”（表示已删除的文件）。
		// TODO（#39958）：处理磁盘上不存在包目录
		// 的情况（当包的所有
		// 文件都在覆盖层中时可能会发生这种情况）：代码期望包目录
		// 存在，并在该目录中运行一些工具。
		// TODO（#39958）：当
		// gofiles、cgofiles、cfiles、sfiles和cxxfiles变量是在（*Builder）中创建的
		// 时，处理覆盖。建筑这样做需要重写
		// 代码，该代码使用这些值来期望绝对路径。
		f, _ = fsys.OverlayPath(f)

		args = append(args, f)
	}

	output, err = b.runOut(a, base.Cwd(), nil, args...)
	return ofile, output, err
}

// gcBackendConcurrency返回包编译的后端编译器并发级别。
func gcBackendConcurrency(gcflags []string) int {
	// 首先，检查我们是否可以使用-c进行编译。
	canDashC := concurrentGCBackendCompilationEnabledByDefault

	switch e := os.Getenv("GO19CONCURRENTCOMPILATION"); e {
	case "0":
		canDashC = false
	case "1":
		canDashC = true
	case "":
		// 未设置。使用默认值。
	default:
		log.Fatalf("GO19CONCURRENTCOMPILATION must be 0, 1, or unset, got %q", e)
	}

CheckFlags:
	for _, flag := range gcflags {
		// 除了已知的常用标志外，并发编译被认为与任何gcflags、
		// 不兼容。
		// 如果用户更清楚，他们可以手动将自己的-c添加到gcflags中。
		switch flag {
		case "-N", "-l", "-S", "-B", "-C", "-I", "-shared":
			// 确定
		default:
			canDashC = false
			break CheckFlags
		}
	}

	// TODO:测试并删除这些条件。
	if buildcfg.Experiment.FieldTrack || buildcfg.Experiment.PreemptibleLoops {
		canDashC = false
	}

	if !canDashC {
		return 1
	}

	// 决定允许多少并发后端编译。
	// 
	// 如果我们允许太多，理论上我们可能会得到p个并发进程，
	// 每个进程都有c并发后端编译，所有这些都在争夺相同的资源。
	// 然而，在实践中，这种情况似乎没有太多发生。
	// 大多数构建图都是令人惊讶的序列图，所以对于大部分构建，p==1。
	// 此外，并发后端编译只对整个编译器执行的一部分
	// 启用，因此对于大部分构建，c==1。
	// 所以现在不要太担心这种互动。
	// 
	// 然而，在实践中，将c设置在4以上往往没有多大帮助。
	// 参见第41192条中的分析。
	// 
	// TODO（约书亚）：尝试检测此特定编译
	// 是否可能成为瓶颈，例如，当：
	// -它没有要编译的后续包（通常是包主）
	// -通过构建图的所有路径都经过它
	// -关键路径调度说它是高优先级的
	// /在这种情况下，将c设置为runtime。GOMAXPROCS（0）。
	// 默认情况下，这与运行时相同。努马普。
	// 我们现在在p=1时这样做。
	// 要限制并行性，请将GOMAXPROCS设置为numCPU以下；在低内存构建器上，或者需要确定的构建顺序时，这可能很有用。
	c := runtime.GOMAXPROCS(0)
	if cfg.BuildP == 1 {
		// 没有进程并行性，不要限制编译器并行性。
		return c
	}
	// 一些进程并行性。将c设置为最小值（4，maxprocs）。
	if c > 4 {
		c = 4
	}
	return c
}

// trimpath返回-trimpath参数，以在编译操作时使用
// 。
func (a *Action) trimpath() string {
	// 与建筑商保持同步。ccompile 
	// 修剪后的路径有点不同，但我们需要在
	// 相同的情况下进行修剪。

	// 完全剥离对象目录。
	objdir := a.Objdir
	if len(objdir) > 1 && objdir[len(objdir)-1] == filepath.Separator {
		objdir = objdir[:len(objdir)-1]
	}
	rewrite := ""

	rewriteDir := a.Package.Dir
	if cfg.BuildTrimpath {
		importPath := a.Package.Internal.OrigImportPath
		if m := a.Package.Module; m != nil && m.Version != "" {
			rewriteDir = m.Path + "@" + m.Version + strings.TrimPrefix(importPath, m.Path)
		} else {
			rewriteDir = importPath
		}
		rewrite += a.Package.Dir + "=>" + rewriteDir + ";"
	}

	// 为覆盖添加重写。覆盖中的“from”和“to”路径不需要有相同的基名，所以从覆盖内容文件路径（传递给编译器）到磁盘路径将被重写的路径。

	cgoFiles := make(map[string]bool)
	for _, f := range a.Package.CgoFiles {
		cgoFiles[f] = true
	}

	// TODO（matloob）：在堆栈的更高层，当决定何时复制c/c++/m/f/hfiles的
	// 的逻辑被合并时，使用Build用于确定
	// 是否在objdir中创建副本的相同逻辑来决定是否将objdir重写到此处的
	// 包目录。
	var overlayNonGoRewrites string // 非go文件的重写
	hasCgoOverlay := false
	if fsys.OverlayFile != "" {
		for _, filename := range a.Package.AllFiles() {
			path := filename
			if !filepath.IsAbs(path) {
				path = filepath.Join(a.Package.Dir, path)
			}
			base := filepath.Base(path)
			isGo := strings.HasSuffix(filename, ".go") || strings.HasSuffix(filename, ".s")
			isCgo := cgoFiles[filename] || !isGo
			overlayPath, isOverlay := fsys.OverlayPath(path)
			if isCgo && isOverlay {
				hasCgoOverlay = true
			}
			if !isCgo && isOverlay {
				rewrite += overlayPath + "=>" + filepath.Join(rewriteDir, base) + ";"
			} else if isCgo {
				// 为复制到objdir中的文件的非go文件生成重写。
				if filepath.Dir(path) == a.Package.Dir {
					// 这是一个复制到objdir的文件。
					overlayNonGoRewrites += filepath.Join(objdir, base) + "=>" + filepath.Join(rewriteDir, base) + ";"
				}
			} else {
				// 非重叠Go文件包含在a.Package中。上面的Dir重写规则。
			}
		}
	}
	if hasCgoOverlay {
		rewrite += overlayNonGoRewrites
	}
	rewrite += objdir + "=>"

	return rewrite
}

func asmArgs(a *Action, p *load.Package) []any {
	// Add-I pkg/GOOS#u GOARCH so#include“textflag.h”在中工作。这是我的文件。
	inc := filepath.Join(cfg.GOROOT, "pkg", "include")
	pkgpath := pkgPath(a)
	args := []any{cfg.BuildToolexec, base.Tool("asm"), "-p", pkgpath, "-trimpath", a.trimpath(), "-I", a.Objdir, "-I", inc, "-D", "GOOS_" + cfg.Goos, "-D", "GOARCH_" + cfg.Goarch, forcedAsmflags, p.Internal.Asmflags}
	if p.ImportPath == "runtime" && cfg.Goarch == "386" {
		for _, arg := range forcedAsmflags {
			if arg == "-dynlink" {
				args = append(args, "-D=GOBUILDMODE_shared=1")
			}
		}
	}
	if objabi.IsRuntimePackagePath(pkgpath) {
		args = append(args, "-compiling-runtime")
	}

	if cfg.Goarch == "386" {
		// 从cfg定义GO386_值。GO386。
		args = append(args, "-D", "GO386_"+cfg.GO386)
	}

	if cfg.Goarch == "amd64" {
		// 从cfg中定义GOAMD64_值。目标64。
		args = append(args, "-D", "GOAMD64_"+cfg.GOAMD64)
	}

	if cfg.Goarch == "mips" || cfg.Goarch == "mipsle" {
		// 从cfg定义GOMIPS_值。戈米普斯。
		args = append(args, "-D", "GOMIPS_"+cfg.GOMIPS)
	}

	if cfg.Goarch == "mips64" || cfg.Goarch == "mips64le" {
		// 从cfg定义GOMIPS64_值。戈米普斯64。
		args = append(args, "-D", "GOMIPS64_"+cfg.GOMIPS64)
	}

	return args
}

func (gcToolchain) asm(b *Builder, a *Action, sfiles []string) ([]string, error) {
	p := a.Package
	args := asmArgs(a, p)

	var ofiles []string
	for _, sfile := range sfiles {
		overlayPath, _ := fsys.OverlayPath(mkAbs(p.Dir, sfile))
		ofile := a.Objdir + sfile[:len(sfile)-len(".s")] + ".o"
		ofiles = append(ofiles, ofile)
		args1 := append(args, "-o", ofile, overlayPath)
		if err := b.run(a, p.Dir, p.ImportPath, nil, args1...); err != nil {
			return nil, err
		}
	}
	return ofiles, nil
}

func (gcToolchain) symabis(b *Builder, a *Action, sfiles []string) (string, error) {
	mkSymabis := func(p *load.Package, sfiles []string, path string) error {
		args := asmArgs(a, p)
		args = append(args, "-gensymabis", "-o", path)
		for _, sfile := range sfiles {
			if p.ImportPath == "runtime/cgo" && strings.HasPrefix(sfile, "gcc_") {
				continue
			}
			op, _ := fsys.OverlayPath(mkAbs(p.Dir, sfile))
			args = append(args, op)
		}

		// 提供一个空的密码。就好像编译器已经运行过一样。
		// gensymabis解析非常松散，我们不需要go_asm中出现的实际定义。h、 
		if err := b.writeFile(a.Objdir+"go_asm.h", nil); err != nil {
			return err
		}

		return b.run(a, p.Dir, p.ImportPath, nil, args...)
	}

	var symabis string // 仅当我们实际创建文件
	p := a.Package
	if len(sfiles) != 0 {
		symabis = a.Objdir + "symabis"
		if err := mkSymabis(p, sfiles, symabis); err != nil {
			return "", err
		}
	}

	return symabis, nil
}

// toolVerify检查命令行args是否写入相同的输出文件
// 如果改用newTool运行。
// 现在未使用，但保留下来以备将来使用。
func toolVerify(a *Action, b *Builder, p *load.Package, newTool string, ofile string, args []any) error {
	newArgs := make([]any, len(args))
	copy(newArgs, args)
	newArgs[1] = base.Tool(newTool)
	newArgs[3] = ofile + ".new" // x.6变为x.6。新的
	if err := b.run(a, p.Dir, p.ImportPath, nil, newArgs...); err != nil {
		return err
	}
	data1, err := os.ReadFile(ofile)
	if err != nil {
		return err
	}
	data2, err := os.ReadFile(ofile + ".new")
	if err != nil {
		return err
	}
	if !bytes.Equal(data1, data2) {
		return fmt.Errorf("%s and %s produced different output files:\n%s\n%s", filepath.Base(args[1].(string)), newTool, strings.Join(str.StringList(args...), " "), strings.Join(str.StringList(newArgs...), " "))
	}
	os.Remove(ofile + ".new")
	return nil
}

func (gcToolchain) pack(b *Builder, a *Action, afile string, ofiles []string) error {
	var absOfiles []string
	for _, f := range ofiles {
		absOfiles = append(absOfiles, mkAbs(a.Objdir, f))
	}
	absAfile := mkAbs(a.Objdir, afile)

	// 存档文件应该是由编译器创建的。
	// 因为它以前不是这样工作的，所以请验证。
	if !cfg.BuildN {
		if _, err := os.Stat(absAfile); err != nil {
			base.Fatalf("os.Stat of archive file failed: %v", err)
		}
	}

	p := a.Package
	if cfg.BuildN || cfg.BuildX {
		cmdline := str.StringList(base.Tool("pack"), "r", absAfile, absOfiles)
		b.Showcmd(p.Dir, "%s # internal", joinUnambiguously(cmdline))
	}
	if cfg.BuildN {
		return nil
	}
	if err := packInternal(absAfile, absOfiles); err != nil {
		b.showOutput(a, p.Dir, p.Desc(), err.Error()+"\n")
		return errPrintedOutput
	}
	return nil
}

func packInternal(afile string, ofiles []string) error {
	dst, err := os.OpenFile(afile, os.O_WRONLY|os.O_APPEND, 0)
	if err != nil {
		return err
	}
	defer dst.Close() // 仅适用于错误返回或恐慌
	w := bufio.NewWriter(dst)

	for _, ofile := range ofiles {
		src, err := os.Open(ofile)
		if err != nil {
			return err
		}
		fi, err := src.Stat()
		if err != nil {
			src.Close()
			return err
		}
		// 注意：不使用%-16.16s格式，因为我们关心
		// 字节，而不是符文。
		name := fi.Name()
		if len(name) > 16 {
			name = name[:16]
		} else {
			name += strings.Repeat(" ", 16-len(name))
		}
		size := fi.Size()
		fmt.Fprintf(w, "%s%-12d%-6d%-6d%-8o%-10d`\n",
			name, 0, 0, 0, 0644, size)
		n, err := io.Copy(w, src)
		src.Close()
		if err == nil && n < size {
			err = io.ErrUnexpectedEOF
		} else if err == nil && n > size {
			err = fmt.Errorf("file larger than size reported by stat")
		}
		if err != nil {
			return fmt.Errorf("copying %s to %s: %v", ofile, afile, err)
		}
		if size&1 != 0 {
			w.WriteByte(0)
		}
	}

	if err := w.Flush(); err != nil {
		return err
	}
	return dst.Close()
}

// setextld为指定的编译器设置适当的链接器标志。
func setextld(ldflags []string, compiler []string) ([]string, error) {
	for _, f := range ldflags {
		if f == "-extld" || strings.HasPrefix(f, "-extld=") {
			// 如果提供了extld，则不要覆盖-extld 
			return ldflags, nil
		}
	}
	joined, err := quoted.Join(compiler)
	if err != nil {
		return nil, err
	}
	return append(ldflags, "-extld="+joined), nil
}

// pluginPath计算插件主包的包路径。
// 
// 这通常是主包p的导入路径，除非
// 插件是直接从源文件构建的。在这种情况下，我们
// 将包构建ID与主包
// 源文件的内容结合起来。这允许我们识别两个不同的插件
// 由两个同名源文件构建。
func pluginPath(a *Action) string {
	p := a.Package
	if p.ImportPath != "command-line-arguments" {
		return p.ImportPath
	}
	h := sha1.New()
	buildID := a.buildID
	if a.Mode == "link" {
		// 对于链接，使用主包的构建ID而不是
		// 二进制文件的构建ID，因此它与
		// 编译和链接中使用的哈希相同。
		// 编译时，我们使用actionID/actionID（而不是
		// actionID/contentID）作为临时构建ID来计算
		// 哈希。在这里也一样。（请参见buildid.go:useCache）
		// 构建ID很重要，因为它会影响下面返回的插件伪导入路径中的整体哈希
		// 。
		// 编译和链接时需要使用相同的导入路径。
		id := strings.Split(buildID, buildIDSeparator)
		buildID = id[1] + buildIDSeparator + id[1]
	}
	fmt.Fprintf(h, "build ID: %s\n", buildID)
	for _, file := range str.StringList(p.GoFiles, p.CgoFiles, p.SFiles) {
		data, err := os.ReadFile(filepath.Join(p.Dir, file))
		if err != nil {
			base.Fatalf("go: %s", err)
		}
		h.Write(data)
	}
	return fmt.Sprintf("plugin/unnamed-%x", h.Sum(nil))
}

func (gcToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) error {
	cxx := len(root.Package.CXXFiles) > 0 || len(root.Package.SwigCXXFiles) > 0
	for _, a := range root.Deps {
		if a.Package != nil && (len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0) {
			cxx = true
		}
	}
	var ldflags []string
	if cfg.BuildContext.InstallSuffix != "" {
		ldflags = append(ldflags, "-installsuffix", cfg.BuildContext.InstallSuffix)
	}
	if root.Package.Internal.OmitDebug {
		ldflags = append(ldflags, "-s", "-w")
	}
	if cfg.BuildBuildmode == "plugin" {
		ldflags = append(ldflags, "-pluginpath", pluginPath(root))
	}

	// 将BuildID存储在工具链二进制文件中，作为正在运行的
	// 工具的唯一标识符，供基于内容的过时判定使用。
	if root.Package.Goroot && strings.HasPrefix(root.Package.ImportPath, "cmd/") {
		// 外部链接将在外部
		// 链接器的构建id中包含我们的构建id，这将导致我们的构建id在下次构建工具时不匹配。
		// 改用外部生成id。
		if !sys.MustLinkExternal(cfg.Goos, cfg.Goarch) {
			ldflags = append(ldflags, "-X=cmd/internal/objabi.buildID="+root.buildID)
		}
	}

	// 如果用户没有指定-extld选项，那么请指定相应的链接器。在C++代码的情况下，如果没有设置CXX，则使用名为ABCDEG
	// 否则，使用CC环境变量和defaultCC作为回退。
	var compiler []string
	if cxx {
		compiler = envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
	} else {
		compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
	}
	ldflags = append(ldflags, "-buildmode="+ldBuildmode)
	if root.buildID != "" {
		ldflags = append(ldflags, "-buildid="+root.buildID)
	}
	ldflags = append(ldflags, forcedLdflags...)
	ldflags = append(ldflags, root.Package.Internal.Ldflags...)
	ldflags, err := setextld(ldflags, compiler)
	if err != nil {
		return err
	}

	// 在OS X上使用外部链接构建共享库时，
	// 此处传递给-o的参数最终记录在LC_ID_DYLIB load命令中的最终
	// 共享库中。
	// 为了避免将临时输出目录名放在那里
	// （并使生成的共享库无用），
	// 运行输出目录中的链接，以便-o可以将
	// 命名为最终路径元素。
	// 在Windows上，DLL文件名记录在PE文件
	// 导出部分，在OS X上也一样。
	dir := "."
	if (cfg.Goos == "darwin" || cfg.Goos == "windows") && cfg.BuildBuildmode == "c-shared" {
		dir, out = filepath.Split(out)
	}

	env := []string{}
	if cfg.BuildTrimpath {
		env = append(env, "GOROOT_FINAL="+trimPathGoRootFinal)
	}
	return b.run(root, dir, root.Package.ImportPath, env, cfg.BuildToolexec, base.Tool("link"), "-o", out, "-importcfg", importcfg, ldflags, mainpkg)
}

func (gcToolchain) ldShared(b *Builder, root *Action, toplevelactions []*Action, out, importcfg string, allactions []*Action) error {
	ldflags := []string{"-installsuffix", cfg.BuildContext.InstallSuffix}
	ldflags = append(ldflags, "-buildmode=shared")
	ldflags = append(ldflags, forcedLdflags...)
	ldflags = append(ldflags, root.Package.Internal.Ldflags...)
	cxx := false
	for _, a := range allactions {
		if a.Package != nil && (len(a.Package.CXXFiles) > 0 || len(a.Package.SwigCXXFiles) > 0) {
			cxx = true
		}
	}
	// 如果用户未指定-extld选项，则指定
	// 适当的链接器。在C++代码的情况下，如果没有设置CXX，则使用名为ABCDEG
	// 否则，使用CC环境变量和defaultCC作为回退。
	var compiler []string
	if cxx {
		compiler = envList("CXX", cfg.DefaultCXX(cfg.Goos, cfg.Goarch))
	} else {
		compiler = envList("CC", cfg.DefaultCC(cfg.Goos, cfg.Goarch))
	}
	ldflags, err := setextld(ldflags, compiler)
	if err != nil {
		return err
	}
	for _, d := range toplevelactions {
		if !strings.HasSuffix(d.Target, ".a") { // 忽略其他共享库的不安全etc和操作
			continue
		}
		ldflags = append(ldflags, d.Package.ImportPath+"="+d.Target)
	}
	return b.run(root, ".", out, nil, cfg.BuildToolexec, base.Tool("link"), "-o", out, "-importcfg", importcfg, ldflags)
}

func (gcToolchain) cc(b *Builder, a *Action, ofile, cfile string) error {
	return fmt.Errorf("%s: C source files not supported without cgo", mkAbs(a.Package.Dir, cfile))
}
